package beat

import (
	"fmt"
	"log"
	"math"
	"math/rand"
	"time"

	"gitee.com/uxff/beatdemo/util"
)

type One struct {
	Id   int32
	Tag  string
	Role string //role //enum: T|A|H|B

	MaxH float32
	H    float32
	MaxM float32
	M    float32

	Power struct {
		Base    float32 //[0,+inf] //increase: BasePower*(1.1|1.2|1.3|1.4)
		CriRate float32 //[0,1.0]  //probability of cri, P*=CriMul //increase: 0.2|0.3|0.4|0.5
		EERate  float32 //[0,1.0+] //elastic(random) exceed P+=P*random expansion //increase: 0.3|0.5|0.7|0.9
		PnRate  float32 //[0,1.0] //reduct target.armor //increase: BasePenetrate+(0.1|0.2|0.3|0.4) //effectual in pvp
		CriMul  float32 //[2.0,3.0]  //double P*=CriMul //increase: 2.1|2.2|2.3|2.4

		//HitRate float32 //[0,1.0] //hit rate

		ExecPeriod float32 // μs
		ThreatRate float32
	}

	Defend struct {
		ERRate float32 //elastic(random) resist defend against some dmg
		DeCri  float32 //probability, random supress target critical rate //effectual in pvp
		Amor   float32 //reduct target dmg //[BaseMaxH*0.10*1.1, BasemaxH*0.10*1.4]
		Dodge  float32 //probability, random ignore target dmg //for T:[0.1|0.2|0.3|0.4] //应当要有T三围 格挡、招架、躲闪+未命中
	}

	levelRate float32 //base rateLevel

	recvQ chan Dmg
	// done  chan bool

	OutObserver []func(d Dmg) //callback when recv dmg
	// dieYell chan bool //TODO yell out for over main process

	started bool
}

var oneIdx int32 = 0

func NewOne() *One {
	oneIdx++

	one := &One{
		Id:  oneIdx,
		Tag: "common",

		MaxH: BaseMaxH,
		H:    BaseMaxH,
		// MaxM: BaseMaxM,
		// M:    BaseMaxM,

		levelRate: 1.0,

		recvQ: make(chan Dmg, 10),
		// done:  make(chan bool, 1),
	}

	one.Power.Base = BasePower
	one.Power.CriRate = BaseCriticalRate
	one.Power.EERate = BaseElasticExceedRate
	one.Power.ExecPeriod = BasePeriod
	one.Power.ThreatRate = BaseThreatRate

	one.Defend.Amor = BaseAmor
	one.Defend.DeCri = BaseDeCritical
	one.Defend.ERRate = BaseElasticResistRate

	return one
}

//standBy
func (one *One) StandBy() {
	if one.started == false {
		one.started = true
		go one.suffering()
		log.Printf("i(%s) am ready!", one.ToString())
	}
}

func (one *One) ToString() string {
	return fmt.Sprintf("%d/%s/%.0f", one.Id, one.Tag, one.H)
}

func (one *One) suffering() {
	for one.H > 0 {
		select {
		case d := <-one.recvQ:
			switch d.T {
			case DmgTypeHurt: //dmg
				var dmgPower, elasticResist, armoredPower float32 = d.P, 0, 0
				recvRand := rand.Float32()

				dodgeRand := recvRand
				if dodgeRand < one.Defend.Dodge {
					//whole dodge, no dmg
					dmgPower = 0
				} else {
					deCriRand := util.MixRandAuto(recvRand)

					if d.IsCritical() {
						if deCriRand < one.Defend.DeCri {
							dmgPower /= 2
						}
					}

					resistRand := util.MixRandAuto(recvRand) //rand.Float32()
					elasticResist = resistRand * one.Defend.ERRate * dmgPower

					dmgPower -= elasticResist
					//参考护甲伤害减免伤害公式:伤害减免比例=(护甲值×护甲变量常数)÷(1+护甲变量常数×护甲值)
					effectArmor := one.Defend.Amor * (1.0 - d.Pn)
					armoredPower = dmgPower * (effectArmor / (effectArmor + BaseMaxH))
					dmgPower -= armoredPower
					if dmgPower < 0 {
						dmgPower = 0
					}
				}

				d.SetResist(d.P - dmgPower)
				one.H -= dmgPower

				log.Printf("i(%s) suffered %.1f %+v. (eResist:%.1f armored:%.1f)", one.ToString(), dmgPower, d, elasticResist, armoredPower)

				if one.H <= 0 {
					one.H = 0
					log.Printf("i(%s) die.", one.ToString())
					// one.done <- true
				}
			case DmgTypeHeal: //heal
				healPower := d.P
				one.H += healPower
				if one.H > one.MaxH {
					one.H = one.MaxH
				}
				log.Printf("i(%s) got a charge:%.1f from %d ++++++++++", one.ToString(), healPower, d.From)
			default:
				// log.Printf("i(%s) got a threat:%.1f from %d T:%d", one.ToString(), d.P, d.From, d.T)
			}

			// log.Printf("one(%s) before outObserver-----------> d.P %v. remain:%d", one.ToString(), d.P, len(one.recvQ))
			if len(one.OutObserver) > 0 {
				for _, f := range one.OutObserver {
					go f(d)
				}
			}
			// log.Printf("one(%s) after outObserver<----------- d.P %v. remain:%d", one.ToString(), d.P, len(one.recvQ))
		}
	}
	one.started = false
}

//rate: [1.0, 2.0] 1.1|1.2|1.3|1.4
func (one *One) UpPowerByRate(rate float32) {
	one.levelRate = rate
	one.MaxH = BaseMaxH * rate
	one.Power.Base = BasePower * rate
	one.Power.CriRate = BaseCriticalRate + (rate-1.0)*0.8   //0.16+0.08|0.16+0.16|0.16+0.24|0.16+0.32
	one.Power.EERate = BaseElasticExceedRate + (rate-1.0)*2 //0.3|0.5|0.7|0.9
	one.Power.PnRate = BasePenetrateRate + (rate-1.0)*1.6   // 1.1|1.2|1.3|1.4
	one.Power.ExecPeriod = BasePeriod / rate
	one.Power.CriMul = BaseCriticalMulti + (rate - 1.0) //2.1|2.2|2.3|2.4
	one.Defend.DeCri = BaseDeCritical * rate            //[1.0*0.2 - 2.0*0.2]
	one.Defend.Amor = BaseAmor * rate * rate            //BaseMaxH*constRate*(1.1|1.2|1.3|1.4)
	one.Defend.ERRate = BaseElasticResistRate * rate    //[1.0*0.13 - 2.0*0.13]
	if one.Power.PnRate > 1.0 {
		one.Power.PnRate = 0.99999
	}
}

func (one *One) UpToLv(lv string) {
	one.Tag, one.levelRate = lv, RateLvMap[lv]
	randRoll := (rand.Float32() - 0.5) / 40
	one.UpPowerByRate(one.levelRate + randRoll)
}

func (one *One) UpAsBot(rate float32, pNum int, tint float32) {
	one.Role = RoleB
	one.UpPowerByRate(rate)
	one.MaxH = (BaseMaxH * one.levelRate) * float32(pNum) * float32(math.Sqrt(float64(pNum))) * one.levelRate * one.levelRate * one.levelRate * tint //0.224 bigger is harder, more h of boss
	// one.MaxH = ((BaseMaxH*one.levelRate)*float32(pNum)*float32(math.Sqrt(float64(pNum)))*one.levelRate*one.levelRate*one.levelRate + BaseMaxH*one.levelRate*one.levelRate*float32(pNum*pNum)/2) * tint //0.224 bigger is harder, more h of boss
	one.Defend.DeCri, one.Defend.Amor = one.Defend.DeCri*0.1, one.Defend.Amor*one.levelRate*float32(math.Sqrt(float64(one.levelRate)))
	// one.Defend.Amor = one.Defend.Amor * 2   //one.Defend.Amor * one.levelRate * 1.5
	one.Power.Base *= (one.levelRate * 1.5) //NOTICE: this param directly decides the power of a boss
	one.Power.EERate, one.Power.CriRate, one.Power.PnRate, one.Power.CriMul = one.Power.EERate/2, one.Power.CriRate/16, 0, one.Power.CriMul/1.5
}

func (one *One) Rotate(role string) {
	one.Role = role
	switch role {
	case RoleT:
		one.MaxH *= 2
		one.Power.Base *= 0.25  //0.5-0.6
		one.Power.EERate *= 0.2 //0.06|0.1|0.14|0.18
		one.Power.ThreatRate = one.levelRate * one.levelRate * 5
		one.Defend.Amor *= 5     //* one.levelRate //[2.0*0.1*2 - 2.0*0.1*2] = [0.2 - 0.4]
		one.Defend.ERRate *= 2.5 ////[1.0*0.13*1.5 - 2.0*0.13*1.5] = [0.19 - 0.39]
		one.Defend.DeCri *= 0.1  //
		one.Defend.Dodge = (one.levelRate - 0.9) * 1.6
		if one.Defend.Dodge > 1.0 {
			one.Defend.Dodge = 0.99999
		}
	case RoleH:
		one.Power.ThreatRate *= 0.4
	default:
	}
}

func (one *One) RescueFull() {
	one.H, one.M = one.MaxH, one.MaxM
}

func (one *One) Bear(d Dmg) bool {
	d.To = one.Id
	if one.H <= 0 {
		return false
	}
	go func() {
		one.recvQ <- d
	}()
	return true
}

//hit will spend time
func (one *One) Hit(target *One) bool {
	d := one.MakeDmg(DmgTypeHurt, 1.0)
	if one.H <= 0 || target.H <= 0 {
		return false
	}
	log.Printf("i(%s) will consume(%s) with %+v after %.0f", one.ToString(), target.ToString(), d, one.Power.ExecPeriod)
	time.Sleep(time.Microsecond * time.Duration(one.Power.ExecPeriod))
	if one.H <= 0 || target.H <= 0 {
		return false
	}
	return target.Bear(d)
}

//hit will spend time
func (one *One) Aoe(pmap map[int32]*One, multiple float32) bool {
	d := one.MakeDmg(DmgTypeHurt, multiple)
	if one.H <= 0 {
		return false
	}
	d.Tx = 2
	log.Printf("i(%s) will aoe with %+v after %.0f", one.ToString(), d, one.Power.ExecPeriod)
	time.Sleep(time.Microsecond * time.Duration(one.Power.ExecPeriod))
	if one.H <= 0 {
		return false
	}
	oddOrEven := int(d.P) % 2
	i := 0
	beared := 0
	for id := range pmap {
		i++
		if i%2 == oddOrEven {
			if pmap[id].Bear(d) {
				beared++
			}
		}
	}
	return beared > 0
}

//heal will spend time
func (one *One) Heal(target *One) bool {
	d := one.MakeDmg(DmgTypeHeal, 1.0)
	if one.H <= 0 || target.H <= 0 {
		return false
	}
	log.Printf("i(%s) will charge(%s) with %+v after %.0f", one.ToString(), target.ToString(), d, one.Power.ExecPeriod/2)
	time.Sleep(time.Microsecond * time.Duration(one.Power.ExecPeriod) / 2)
	if one.H <= 0 || target.H <= 0 {
		return false
	}

	b := target.Bear(d)
	return b
}

//@param multiple default to be 1.0
func (one *One) MakeDmg(t int, multiple float32) Dmg {
	rand.Seed(time.Now().UnixNano() + int64(one.Id))

	randomExpansionRoll := rand.Float32()
	criticalRoll := util.MixRandAuto(randomExpansionRoll)                                //for reuse rand
	finalFloatRateRoll := (util.MixRandAuto(randomExpansionRoll)-0.5)*FloatingRate + 1.0 //[0.95/1.05]

	isCri := criticalRoll < one.Power.CriRate

	d := Dmg{
		P:    one.Power.Base * multiple,
		From: one.Id,
		T:    t,
		Tx:   one.Power.ThreatRate,
		Pn:   one.Power.PnRate,
		base: one.Power.Base * multiple,
	}

	//eerate将提高dmg幅度42%以上
	d.P = d.P + d.base*randomExpansionRoll*one.Power.EERate
	if isCri {
		//cri和criMul两项综合提高dmg幅度40%以上
		d.criMul = one.Power.CriMul
		if criticalRoll < one.Power.CriRate/2 {
			d.criMul *= 2
			// d.criMul *= one.Power.CriMul
		}
		// if criticalRoll < one.Power.CriRate/4 {
		// 	d.criMul *= 2.55
		// }
		d.P = d.P * d.criMul
	}

	d.P = d.P * finalFloatRateRoll
	// log.Printf("i(%s) make a dmg:%v CriRate:%v finalFloatRate:%v i:+%v", one.ToString(), d, criticalRateRoll, finalFloatRateRoll, one)

	return d
}

func (o *One) ThreatTo(target *One) {
	//put resist/h which i suffered to threatOne by
	o.OutObserver = append(o.OutObserver, func(iSuffered Dmg) {
		switch iSuffered.T {
		case DmgTypeHeal:
			dThreatHeal := Dmg{P: iSuffered.GetBeared(), From: iSuffered.From, T: DmgTypeThreatHeal, Tx: BaseThreatRate}
			target.Bear(dThreatHeal)
		case DmgTypeHurt:
			//isResist
			if iSuffered.From == target.Id {
				dThreatResist := Dmg{P: iSuffered.GetResist(), From: o.Id, T: DmgTypeThreatResist, Tx: o.Power.ThreatRate}
				target.Bear(dThreatResist)
			}
		}
	})
}
